# 使用perf stat，根据选项-I设定测量counting的间隔，探索counting间隔时间对测量结果的影响
# 实验思路：使用sleep 10，测量instructions这个event，观察间隔缩短后对计数总数的影响
# perf指令：perf stat -e cpu/event=0xc0,umask=0x00,name=my_insts/ -a -I 1000 -x, -o output_file prog
# (备注：选项-e，获得raw data，但是选项-A会合并8个LCPU的原始数据，选项-a，对于所有C)


import os
import re
from typing import Iterator, Pattern
import matplotlib.pyplot as plt
import pandas as pd


def run_stat_interval_test(program: str, interval_list: list, event: str, output_path: str):
    """
    调用perf stat完成测试，结果输出到指定文件中
    :param program: 待测程序
    :param interval: 测量间隔列表，单位ms
    :param event: 测量event的描述符字符串
    :param output_path: 测试结果输出目录
    """
    for interval in interval_list:
        os.system("perf stat -e {} -I {} -x, -o {}/{}ms {}".format(event,
                                                                        interval,
                                                                        output_path,
                                                                        interval,
                                                                        program))


def format_test_result(output_path: str) -> dict:
    """
    测试结果完成之后，整理成后续方便处理dataframe格式的数据结构
    :param output_path: 测试结果输出目录
    :return 字典，key是测量间隔，value是相应的dataframe结果，dataframe有2列，time和counting
    """
    results = {}
    for root, ds, fs in os.walk(output_path):
        for f in fs:
            result_file_name = os.path.join(root, f)
            results[f] = get_dataframe(file_path=result_file_name)
            results[f].columns = ["time", "event_num"]
    return results


def get_dataframe(file_path: str):
    """
    将perf stat的输出结果转换为容易被使用的dataframe
    """
    df = pd.read_csv(file_path, skiprows=2, usecols=[0, 1], header=None)
    return df


def data_analysis_1(results: pd.DataFrame, interval_list: list, output_path: str):
    """
    分析采集间隔，采集样本点数量，第一个采集点发生时间，最后一个采集点发生时间，实际平均采集间隔，采集结果求和
    :param results: format_test_result()的返回结果
    :param interval_list: 测量间隔列表
    :param output_path: 分析结果保存到csv文件的路径
    """
    analysis_list = []
    for interval in interval_list:
        entry = []
        result: pd.DataFrame = results["{}ms".format(interval)]
        entry.append(interval)
        entry.append(result.shape[0])
        entry.append(result.iloc[0]["time"])
        entry.append(result.iloc[-1]["time"])
        entry.append((result["time"].diff(
            periods=1).sum() / (entry[1] - 1)) * 1000)
        entry.append(result["event_num"].sum())
        analysis_list.append(entry.copy())
    analysis_df = pd.DataFrame(analysis_list, columns=[
                               "interval", "sample_num", "first", "last", "true_interval", "event_num"])
    analysis_df.to_csv(output_path)
    return analysis_df


def data_analysis_2(results: pd.DataFrame, interval_list: list, output_path: str):
    """
    分析采集间隔，极差，标准差，但是测量的间隔不同，不同测量间隔的数据不具有可比性，可以既然前面已经给出了测量间隔，可以考察测量单位时间内event数量序列的稳定性。
    因此导出单位时间内event次数的序列，通过这个序列计算波动性。
    :param results: format_test_result()的返回结果
    :param interval_list: 测量间隔列表
    :param output_path: 分析结果保存到csv文件的路径
    """
    analysis_list = []
    for interval in interval_list:
        entry = []
        result: pd.DataFrame = results["{}ms".format(interval)]
        entry.append(interval)
        entry.append(result.shape[0])
        entry.append((result["event_num"].max() -
                     result["event_num"].min()) / entry[1])
        entry.append(result["event_num"].std())

        result["event_cumsum"] = result["event_num"].cumsum(axis=0)
        result["event_per_sec"] = result["event_cumsum"] / result["time"]
        entry.append(result["event_per_sec"].std())

        analysis_list.append(entry.copy())
    analysis_df = pd.DataFrame(analysis_list, columns=[
                               "interval", "sample_num", "max-min", "std", "event_per_sec_std"])
    analysis_df.to_csv(output_path)
    return analysis_df


def draw_graphic_event_per_second(results: pd.DataFrame, interval_list: list, fig_name: str):
    """
    绘制单位时间内event数量随时间的变化图
    """
    figure, ax = plt.subplots(3, 3, figsize=(15, 15))
    figure.suptitle(fig_name)
    for index, interval in enumerate(interval_list):
        result: pd.DataFrame = results["{}ms".format(interval)]
        result["event_cumsum"] = result["event_num"].cumsum(axis=0)
        result["event_per_sec"] = result["event_cumsum"] / result["time"]
        plot_row = index // 3
        plot_col = index % 3
        ax[plot_row][plot_col].set_title("{}ms".format(interval))
        result.plot(x="time", y="event_per_sec",
                    kind="line", ax=ax[plot_row][plot_col])
    figure.savefig(fig_name + ".jpg", dpi=300, format="jpg")


def draw_graphic_event_cumsum(results: pd.DataFrame, interval_list: list, fig_name: str):
    """
    绘制单位时间内累积event数量随时间的变化图
    """
    figure, ax = plt.subplots(3, 3, figsize=(15, 15))
    figure.suptitle(fig_name)
    for index, interval in enumerate(interval_list):
        result: pd.DataFrame = results["{}ms".format(interval)]
        result["event_cumsum"] = result["event_num"].cumsum(axis=0)
        result["event_per_sec"] = result["event_cumsum"] / result["time"]
        plot_row = index // 3
        plot_col = index % 3
        ax[plot_row][plot_col].set_title("{}ms".format(interval))
        result.plot(x="time", y="event_cumsum",
                    kind="line", ax=ax[plot_row][plot_col])
    figure.savefig(fig_name + ".jpg", dpi=300, format="jpg")


if __name__ == "__main__":
    interval_list = [1000, 500, 300, 200, 100, 50, 30, 20, 10]
    output_path_inst = "perf_interval_result/raw_data/inst"
    output_path_cycle = "perf_interval_result/raw_data/cycle"
    output_path_cs = "perf_interval_result/raw_data/cs"
    iterator = 1000000000

    # # 测试1：event是instructions
    run_stat_interval_test(program="./workspace/perf_stat_example {}".format(iterator),
                           interval_list=interval_list,
                           event="cpu/event=0xc0,umask=0x00,name=my_insts/",
                           output_path=output_path_inst)
    results = format_test_result(output_path=output_path_inst)
    # 分析部分
    data_analysis_result_1 = data_analysis_1(
        results, interval_list, "perf_interval_result/inst_pi_1.csv")
    print(data_analysis_result_1)
    data_analysis_result_2 = data_analysis_2(
        results, interval_list, "perf_interval_result/inst_pi_2.csv")
    print(data_analysis_result_2)

    draw_graphic_event_per_second(results, interval_list, "perf_interval_result/instructions - event per sec")
    draw_graphic_event_cumsum(results, interval_list, "perf_interval_result/instructions - accumulate event")

    # # 测试2：event是cycles
    run_stat_interval_test(program="./workspace/perf_stat_example {}".format(iterator),
                           interval_list=interval_list,
                           event="cpu/event=0x3c,umask=0x00,name=my_cycles/",
                           output_path=output_path_cycle)
    results = format_test_result(output_path=output_path_cycle)
    # 分析部分
    data_analysis_result_1 = data_analysis_1(
        results, interval_list, "perf_interval_result/cycle_pi_1.csv")
    print(data_analysis_result_1)
    data_analysis_result_2 = data_analysis_2(
        results, interval_list, "perf_interval_result/cycle_pi_2.csv")
    print(data_analysis_result_2)
    draw_graphic_event_per_second(results, interval_list, "perf_interval_result/cycles - event per sec")
    draw_graphic_event_cumsum(results, interval_list, "perf_interval_result/cycles - accumulate event")

    # # 测试3：event是context-switches(是software event)，待测程序是sleep 5
    run_stat_interval_test(program="./workspace/perf_stat_example {}".format(iterator),
                           interval_list=interval_list,
                           event="cs",
                           output_path=output_path_cs)
    results = format_test_result(output_path=output_path_cs)
    # 分析部分
    data_analysis_result_1 = data_analysis_1(
        results, interval_list, "perf_interval_result/cs_pi_1.csv")
    print(data_analysis_result_1)
    data_analysis_result_2 = data_analysis_2(
        results, interval_list, "perf_interval_result/cs_pi_2.csv")
    print(data_analysis_result_2)
    draw_graphic_event_per_second(results, interval_list, "perf_interval_result/context-switch - event per sec")
    draw_graphic_event_cumsum(results, interval_list, "perf_interval_result/context-switch - accumulate event")
